Skip to content

feat(control): add extension sideloading#2547

Open
TomCC7 wants to merge 12 commits into
dimensionalOS:mainfrom
TomCC7:cc/exp/sideloading
Open

feat(control): add extension sideloading#2547
TomCC7 wants to merge 12 commits into
dimensionalOS:mainfrom
TomCC7:cc/exp/sideloading

Conversation

@TomCC7

@TomCC7 TomCC7 commented Jun 20, 2026

Copy link
Copy Markdown
Member

Problem

External robot packages need a supported way to register ControlCoordinator hardware adapter names and control task types without placing files inside the DimOS source tree. Without this, an outside package can define a blueprint but still cannot use HardwareComponent(adapter_type=...) or TaskConfig(type=...) for custom hardware/control unless DimOS itself is modified.

Closes DIM-1031

Solution

Add a public dimos.control.extensions facade for explicit extension registration:

  • register_hardware_adapter(HardwareType, adapter_type, factory) dispatches to the existing manipulator, base, or whole-body adapter registry.
  • register_control_task(task_type, factory_path) registers lazy control task factories without importing the target module at registration time.
  • Hardware and task registries now reject conflicting duplicate registrations while keeping exact same mapping re-registration idempotent.
  • Add docs and a runnable no-hardware external package example that logs registration, adapter construction, task creation, task ticks, and adapter writes.

This keeps blueprint usage unchanged: external packages register before coordinator construction, then use normal HardwareComponent and TaskConfig names.

How to Test

Manual QA:

uv run python examples/external_control_extension/demo_external_control.py

Expected output includes [external_test_robot] logs for:

  • registering BASE/external_test_base
  • registering external_test_drive
  • constructing and connecting ExternalTestBaseAdapter
  • creating and ticking ExternalTestDriveTask
  • writing velocities through the external adapter

Contributor License Agreement

  • I have read and approved the CLA.

Comment thread dimos/hardware/drive_trains/transport/adapter.py Outdated
@TomCC7 TomCC7 marked this pull request as ready for review June 20, 2026 20:20
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a public dimos.control.extensions facade that lets external robot packages register hardware adapters and control tasks into the existing ControlCoordinator registries without modifying the DimOS source tree. It also extracts shared normalization helpers into dimos/hardware/registry_utils.py and dimos/control/tasks/registry_utils.py, and makes all three hardware adapter registries reject conflicting duplicate registrations while remaining idempotent for exact same-factory re-registration.

  • register_hardware_adapter(HardwareType, name, factory) dispatches to the matching built-in adapter registry; register_control_task(type, "module:attr") registers a lazy import path resolved only when the coordinator first instantiates that task type.
  • All registry register() and create() paths now share the same normalize_adapter_name / normalize_task_name helpers (strip + lowercase), closing the previous asymmetry where register() stripped but create() only lowercased.
  • A runnable no-hardware example (examples/external_control_extension/) and corresponding docs demonstrate the full registration-to-coordinator lifecycle.

Confidence Score: 5/5

Safe to merge; the core registration and lookup paths are correct, well-tested, and backward-compatible.

The extension facade, shared normalization utilities, and duplicate-rejection logic are all straightforward and covered by thorough unit and integration tests. No existing callers are broken. The two findings are both in non-production example/utility code and do not affect coordinator correctness.

dimos/control/tasks/registry_utils.py (whitespace validation gap) and examples/external_control_extension/dimos_external_control_extension/blueprints.py (redundant register_extensions call).

Important Files Changed

Filename Overview
dimos/control/extensions.py New public facade for extension registration; dispatches to per-type adapter registries and the control task registry via shared normalization utilities. Clean and well-scoped.
dimos/control/tasks/registry_utils.py New shared helpers for task-name normalization and factory-path validation. Minor gap: whitespace-only module or attribute names pass the path validator and only fail later at import time with a less helpful error message.
dimos/control/tasks/registry.py Updated to use shared normalize/validate helpers and added duplicate-path detection. Behaviour is unchanged for existing callers.
dimos/hardware/registry_utils.py New shared adapter-name normalization (strip + lowercase + empty-check). Eliminates previously duplicated helpers.
dimos/control/test_extensions.py Comprehensive unit tests covering all hardware types, idempotent re-registration, duplicate rejection, padded-name round-trip consistency, and lazy import verification. Registry state is properly restored via autouse fixture.
examples/external_control_extension/dimos_external_control_extension/blueprints.py Example blueprint module; register_extensions() is called both at module-level and inside build_demo_coordinator(), causing duplicate registration log messages in the demo output.
examples/external_control_extension/dimos_external_control_extension/tasks.py Well-structured demo task with proper ResourceClaim, always-active flag, and velocity output.
examples/external_control_extension/dimos_external_control_extension/adapters.py In-memory twist-base adapter with write-count tracking and threading.Event for test synchronization. Clean and correct.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Ext as External Package
    participant Facade as dimos.control.extensions
    participant HWReg as Hardware Registry
    participant TaskReg as ControlTaskRegistry
    participant Coord as ControlCoordinator

    Note over Ext: Module import time
    Ext->>Facade: register_hardware_adapter(BASE, name, factory)
    Facade->>Facade: normalize_adapter_name(name)
    Facade->>HWReg: register(key, factory)
    HWReg-->>Facade: OK (idempotent if same factory)

    Ext->>Facade: register_control_task(type, module:attr)
    Facade->>Facade: normalize_task_name / validate_factory_path
    Facade->>TaskReg: register_path(key, factory_path)
    TaskReg-->>Facade: OK

    Note over Ext: Coordinator construction
    Ext->>Coord: ControlCoordinator(hardware, tasks)

    Note over Coord: coordinator.start()
    Coord->>HWReg: create(adapter_type)
    HWReg-->>Coord: adapter instance

    Coord->>TaskReg: create(task_type, cfg, hardware)
    TaskReg->>TaskReg: importlib.import_module(module)
    TaskReg-->>Coord: task instance
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Ext as External Package
    participant Facade as dimos.control.extensions
    participant HWReg as Hardware Registry
    participant TaskReg as ControlTaskRegistry
    participant Coord as ControlCoordinator

    Note over Ext: Module import time
    Ext->>Facade: register_hardware_adapter(BASE, name, factory)
    Facade->>Facade: normalize_adapter_name(name)
    Facade->>HWReg: register(key, factory)
    HWReg-->>Facade: OK (idempotent if same factory)

    Ext->>Facade: register_control_task(type, module:attr)
    Facade->>Facade: normalize_task_name / validate_factory_path
    Facade->>TaskReg: register_path(key, factory_path)
    TaskReg-->>Facade: OK

    Note over Ext: Coordinator construction
    Ext->>Coord: ControlCoordinator(hardware, tasks)

    Note over Coord: coordinator.start()
    Coord->>HWReg: create(adapter_type)
    HWReg-->>Coord: adapter instance

    Coord->>TaskReg: create(task_type, cfg, hardware)
    TaskReg->>TaskReg: importlib.import_module(module)
    TaskReg-->>Coord: task instance
Loading

Reviews (3): Last reviewed commit: "Apply suggestions from code review" | Re-trigger Greptile

Comment thread dimos/hardware/drive_trains/registry.py Outdated
@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.13483% with 21 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/control/test_extensions.py 91.83% 12 Missing ⚠️
dimos/control/extensions.py 82.14% 3 Missing and 2 partials ⚠️
dimos/control/tasks/registry_utils.py 81.81% 1 Missing and 1 partial ⚠️
dimos/hardware/registry_utils.py 71.42% 1 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #2547      +/-   ##
==========================================
- Coverage   70.79%   70.48%   -0.32%     
==========================================
  Files         862      876      +14     
  Lines       77504    77963     +459     
  Branches     6886     6926      +40     
==========================================
+ Hits        54872    54951      +79     
- Misses      20843    21233     +390     
+ Partials     1789     1779      -10     
Flag Coverage Δ
OS-ubuntu-24.04-arm 63.03% <92.13%> (+0.09%) ⬆️
OS-ubuntu-latest 65.83% <92.13%> (+0.07%) ⬆️
Py-3.10 65.83% <92.13%> (+0.08%) ⬆️
Py-3.11 65.83% <92.13%> (+0.08%) ⬆️
Py-3.12 65.83% <92.13%> (+0.08%) ⬆️
Py-3.13 65.83% <92.13%> (+0.08%) ⬆️
Py-3.14 65.84% <92.13%> (+0.08%) ⬆️
Py-3.14t 65.83% <92.13%> (+0.08%) ⬆️
SelfHosted-macOS ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/control/tasks/registry.py 74.24% <100.00%> (ø)
...control/test_external_control_extension_example.py 100.00% <100.00%> (ø)
dimos/hardware/drive_trains/registry.py 90.47% <100.00%> (+1.90%) ⬆️
dimos/hardware/manipulators/registry.py 85.71% <100.00%> (+3.36%) ⬆️
dimos/hardware/whole_body/registry.py 81.25% <100.00%> (ø)
dimos/control/tasks/registry_utils.py 81.81% <81.81%> (ø)
dimos/hardware/registry_utils.py 71.42% <71.42%> (ø)
dimos/control/extensions.py 82.14% <82.14%> (ø)
dimos/control/test_extensions.py 91.83% <91.83%> (ø)

... and 28 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread docs/capabilities/external_robot_packages.md Outdated
Comment thread docs/capabilities/external_robot_packages.md Outdated
Comment thread docs/capabilities/manipulation/adding_a_custom_arm.md Outdated
Comment thread docs/capabilities/manipulation/adding_a_custom_arm.md Outdated
Comment thread docs/capabilities/manipulation/adding_a_custom_arm.md Outdated
Co-authored-by: cc <55869557+TomCC7@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant